package org.chen.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import lombok.extern.slf4j.Slf4j;
import org.chen.exception.StorageException;
import org.chen.mapper.ArticleElasticsearchRepository;
import org.chen.mapper.ArticleRepository;
import org.chen.model.Article;
import org.chen.model.ImageInfo;
import org.chen.model.Section;
import org.chen.model.elastic.AdminArticleQueryParam;
import org.chen.model.elastic.ArticleIndex;
import org.chen.model.elastic.ArticlePageResult;
import org.chen.model.mongodb.ArticleDocument;
import org.chen.model.vo.ArticleVO;
import org.chen.service.ICrawlerService;
import org.chen.utils.ArticleConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class CrawlerServiceImpl implements ICrawlerService {

    @Autowired
    private ConfigServiceImpl configService;

    private final FileSystemStorageService fileStorage;
    private final ElasticsearchClient esClient;
    private final ArticleRepository articleDocumentRepository;
    private final ArticleElasticsearchRepository articleIndexRepository;

    public CrawlerServiceImpl(FileSystemStorageService fileStorage,
                              ElasticsearchClient esClient,
                              ArticleRepository articleDocumentRepository,
                              ArticleElasticsearchRepository articleIndexRepository) {
        this.fileStorage = fileStorage;
        this.esClient = esClient;
        this.articleDocumentRepository = articleDocumentRepository;
        this.articleIndexRepository = articleIndexRepository;
    }

    public static final Integer STATUS = -1;

    @Override
    public ArticlePageResult getArticles(AdminArticleQueryParam queryParam) {
        try {
            // 1. 构建查询条件
            BoolQuery.Builder boolQuery = new BoolQuery.Builder();

            // 2. 添加查询条件
            // 关键词搜索
            if (StringUtils.hasText(queryParam.getKeyword())) {
                boolQuery.must(new MultiMatchQuery.Builder()
                        .query(queryParam.getKeyword())
                        .fields("title^2", "author^1.5", "category", "tags")
                        .build()._toQuery());
            }

            boolQuery.must(new TermQuery.Builder()
                    .field("source")
                    .value("crawler")
                    .build()._toQuery());

            // 状态筛选
            if (queryParam.getStatus() != null) {
                if (!STATUS.equals(queryParam.getStatus())) {
                    boolQuery.must(new TermQuery.Builder()
                            .field("status")
                            .value(queryParam.getStatus())
                            .build()._toQuery());
                }else {
                    boolQuery.mustNot(new TermQuery.Builder()
                            .field("status")
                            .value(0)
                            .build()._toQuery());
                }
            }

            // 3. 构建排序
            List<SortOptions> sortOptions = new ArrayList<>();

            // 置顶优先
            sortOptions.add(new SortOptions.Builder()
                    .field(new FieldSort.Builder()
                            .field("isTop")
                            .order(SortOrder.Desc)
                            .build())
                    .build());

            // 自定义排序
            if (StringUtils.hasText(queryParam.getSortField())) {
                SortOrder sortOrder = "desc".equalsIgnoreCase(queryParam.getSortOrder()) ?
                        SortOrder.Desc : SortOrder.Asc;
                sortOptions.add(new SortOptions.Builder()
                        .field(new FieldSort.Builder()
                                .field(queryParam.getSortField())
                                .order(sortOrder)
                                .build())
                        .build());
            } else {
                // 默认按创建时间倒序
                sortOptions.add(new SortOptions.Builder()
                        .field(new FieldSort.Builder()
                                .field("createTime")
                                .order(SortOrder.Desc)
                                .build())
                        .build());
            }

            // 4. 构建分页
            int page = Math.max(queryParam.getPage() - 1, 0);
            int size = queryParam.getSize() != null ? queryParam.getSize() : 10;

            // 5. 执行查询
            SearchResponse<ArticleIndex> response = esClient.search(s -> s
                            .index("articles")
                            .query(boolQuery.build()._toQuery())
                            .sort(sortOptions)
                            .from(page * size)
                            .size(size),
                    ArticleIndex.class);

            // 6. 处理结果
            List<ArticleVO> articles = response.hits().hits().stream()
                    .map(hit -> ArticleConverter.toVOFromIndex(hit.source()))
                    .collect(Collectors.toList());

            // 7. 构建返回结果
            assert response.hits().total() != null;
            return ArticlePageResult.builder()
                    .records(articles)
                    .total(response.hits().total().value())
                    .page(queryParam.getPage())
                    .size(size)
                    .build();

        } catch (Exception e) {
            log.error("查询文章列表失败", e);
            throw new RuntimeException("查询文章列表失败", e);
        }
    }

    @Override
    @Transactional
    public void deleteArticle(String id) {
        // 1. 从MongoDB删除
        articleDocumentRepository.deleteById(id);

        // 2. 从Elasticsearch删除
        articleIndexRepository.deleteById(id);
    }

    /**
     * 三种存储方式的特点和用途：
     * <p>
     * 1. MongoDB：
     *    - 存储完整的文章数据和结构化信息
     *    - 适合数据的增删改查
     *    - 用于文章的常规浏览和展示
     * <p>
     * 2. Elasticsearch：
     *    - 专门用于全文搜索
     *    - 支持复杂的搜索条件
     *    - 搜索结果可以高亮显示关键词
     * <p>
     * 3. 文件系统：
     *    - 存储生成的静态HTML文件
     *    - 可以直接通过Web服务器访问
     *    - 适合CDN缓存，提高访问速度
     * <p>
     *    用户浏览场景：
     * <p>
     * 首页展示：从MongoDB获取最新文章列表
     * 文章详情：从文件系统获取静态HTML
     * 相关文章：从Elasticsearch获取相似内容
     * <p>
     *
     *    搜索场景：
     * <p>
     * 用户输入关键词 -> Elasticsearch搜索
     * 获取搜索结果 -> 用MongoDB补充完整信息
     * 显示文章 -> 加载文件系统中的HTML
     * <p>
     *
     *    性能优化：
     * <p>
     * MongoDB：适合数据管理和关系查询
     * Elasticsearch：提供快速搜索和过滤
     * 文件系统：通过CDN提供快速的HTML访问
     */
    @Transactional(rollbackFor = Exception.class)
    public void storeArticle(Article article) {
        List<String> savedFiles = new ArrayList<>();

        try {
            log.info("Storing article with status: {}", article.getStatus());

            // 1. 构建并保存MongoDB文档
            ArticleDocument doc = buildMongoDocument(article);
            doc = articleDocumentRepository.save(doc);
            String articleId = doc.getId();

            // 2. 处理图片
            if (article.getImages() != null && !article.getImages().isEmpty()) {
                List<ImageInfo> processedImages = storeImages(articleId, article.getImages(), savedFiles);
                updateDocumentWithImages(doc, processedImages);
                doc.setContentHtml(buildHtmlContent(article, processedImages));
                doc = articleDocumentRepository.save(doc);
            }

            // 3. 保存到Elasticsearch
            ArticleIndex index = buildEsIndex(doc);
            saveToElasticsearch(index);

            log.info("Article stored successfully: {}", article.getTitle());

        } catch (Exception e) {
            log.error("Error storing article: {}", article.getTitle(), e);
            // 清理已保存的图片
            cleanupSavedImages(savedFiles);
            throw new StorageException("Failed to store article", e);
        }
    }

    // 保存图片文件
    private List<ImageInfo> storeImages(String articleId, List<ImageInfo> images, List<String> savedFiles) {
        String accessPath = configService.getConfigValue("article.crawler.access-path");
        List<ImageInfo> processedImages = new ArrayList<>();

        for (int i = 0; i < images.size(); i++) {
            ImageInfo image = images.get(i);
            ImageInfo processedImage = new ImageInfo();
            String imageIndex = String.format("%02d", i + 1);

            try {
                // 处理缩略图
                if (image.getThumbnailUrl() != null) {
                    String thumbnailFileName = String.format("%s_img%s_thumb.jpg", articleId, imageIndex);
                    fileStorage.saveCrawlerImage(image.getThumbnailUrl(), thumbnailFileName);
                    processedImage.setThumbnailUrl(accessPath + "/" + thumbnailFileName);
                    savedFiles.add(thumbnailFileName);
                }

                // 处理中等尺寸图片
                if (image.getMediumUrl() != null) {
                    String mediumFileName = String.format("%s_img%s_medium.jpg", articleId, imageIndex);
                    fileStorage.saveCrawlerImage(image.getMediumUrl(), mediumFileName);
                    processedImage.setMediumUrl(accessPath + "/" + mediumFileName);
                    savedFiles.add(mediumFileName);
                }

                // 处理大图
                if (image.getLargeUrl() != null) {
                    String largeFileName = String.format("%s_img%s_large.jpg", articleId, imageIndex);
                    fileStorage.saveCrawlerImage(image.getLargeUrl(), largeFileName);
                    processedImage.setLargeUrl(accessPath + "/" + largeFileName);
                    savedFiles.add(largeFileName);
                }

                processedImages.add(processedImage);

            } catch (Exception e) {
                log.error("Error processing image at index {}: {}", i, e.getMessage());
                cleanupSavedImages(savedFiles);
                throw new StorageException("Failed to store images", e);
            }
        }

        return processedImages;
    }

    private void cleanupSavedImages(List<String> savedFiles) {
        for (String fileName : savedFiles) {
            try {
                fileStorage.delete(fileName);
            } catch (Exception e) {
                log.error("Error deleting file {} during cleanup: {}", fileName, e.getMessage());
            }
        }
    }

    private ArticleDocument buildMongoDocument(Article article) {
        ArticleDocument doc = new ArticleDocument();
        // 基本信息
        doc.setTitle(article.getTitle());
        doc.setAuthor(article.getAuthor());
        doc.setCategory(article.getCategory());
        doc.setTimestamp(article.getTimestamp());
        doc.setStatus(article.getStatus());
        // 导语处理
        doc.setSummary(article.getLeadParagraphs().stream()
                .filter(StringUtils::hasText) // 过滤非空导语
                .findFirst() // 获取第一个元素
                .orElse(null)); // 如果没有找到任何导语，返回空列表

        // 状态信息
        doc.setSource("crawler");
        doc.setIsTop(false); //是否置顶
        doc.setIsHot(false); //是否热门
        doc.setIsRecommend(false); //是否推荐
        doc.setViewCount(0L);
        doc.setHotOrder(0);
        doc.setRecommendOrder(0);
        doc.setTopOrder(0);

        // 时间信息
        Date now = new Date();
        doc.setCreateTime(now);
        doc.setUpdateTime(now);

        return doc;
    }

    private ArticleIndex buildEsIndex(ArticleDocument doc) {
        return ArticleIndex.builder()
                .id(doc.getId())
                .title(doc.getTitle())
                .author(doc.getAuthor())
                .category(doc.getCategory())
                .summary(doc.getSummary())
                .thumbnailUrl(doc.getMainImageUrl())
                .createTime(doc.getCreateTime())
                .source(doc.getSource())
                .status(doc.getStatus())
                .isTop(doc.getIsTop())
                .isHot(doc.getIsHot())
                .isRecommend(doc.getIsRecommend())
                .hotOrder(doc.getHotOrder())
                .topOrder(doc.getTopOrder())
                .recommendOrder(doc.getRecommendOrder())
                .viewCount(doc.getViewCount())
//                .tags(doc.getTags())  // 从文章内容提取标签
                .build();
    }

    private void updateDocumentWithImages(ArticleDocument doc, List<ImageInfo> images) {
        if (!images.isEmpty()) {
            ImageInfo mainImage = images.get(0);
            doc.setThumbnailUrl(mainImage.getThumbnailUrl());
            doc.setMainImageUrl(mainImage.getLargeUrl());
            doc.setImages(images);
        }
    }

    private void saveToElasticsearch(ArticleIndex index) throws IOException {

        IndexResponse response = esClient.index(i -> i
                .index("articles")
                .id(index.getId())
                .document(index));

        if (!response.result().name().equals("Created") &&
                !response.result().name().equals("Updated")) {
            throw new RuntimeException("Failed to index article in Elasticsearch");
        }
    }

    private String buildHtmlContent(Article article, List<ImageInfo> images) {
        StringBuilder html = new StringBuilder();
        html.append("<article class='article-container'>");

        // 导语部分
        if (article.getLeadParagraphs() != null && !article.getLeadParagraphs().isEmpty()) {
            html.append("<section class='article-lead'>");
            for (String p : article.getLeadParagraphs()) {
                if (StringUtils.hasText(p)) {
                    html.append("<p class='lead-paragraph'>").append(p).append("</p>");
                }
            }
            html.append("</section>");

            // 主图
            if (images != null && !images.isEmpty()) {
                ImageInfo mainImage = images.get(0);
                appendImage(html, mainImage, 0, true); // true 表示是主图
            }
        }

        // 正文部分
        int imageIndex = 1;
        if (article.getSections() != null) {
            for (Section section : article.getSections()) {
                html.append("<section class='article-section'>");

                // 小标题
                if (StringUtils.hasText(section.getHeading())) {
                    html.append("<h2 class='section-heading'>")
                            .append(section.getHeading())
                            .append("</h2>");
                }

                // 段落和图片交替排列
                List<String> paragraphs = section.getParagraphs();
                if (paragraphs != null) {
                    for (int i = 0; i < paragraphs.size(); i++) {
                        String p = paragraphs.get(i);
                        if (StringUtils.hasText(p)) {
                            html.append("<p class='article-paragraph'>").append(p).append("</p>");
                        }

                        if (i % 3 == 0 && imageIndex < Objects.requireNonNull(images).size()) {
                            appendImage(html, images.get(imageIndex), imageIndex, false);
                            imageIndex++;
                        }
                    }
                }

                html.append("</section>");
            }
        }

        html.append("</article>");
        return html.toString();
    }

    private void appendImage(StringBuilder html, ImageInfo image, int index, boolean isMain) {
        html.append("<figure class='article-image")
                .append(isMain ? " main-image" : "")
                .append("'>");

        // 优先使用大图
        String imgUrl = image.getLargeUrl();
        if (imgUrl == null) {
            imgUrl = image.getMediumUrl();
            if (imgUrl == null) {
                imgUrl = image.getThumbnailUrl();
            }
        }

        if (imgUrl != null) {
            html.append("<img src='")
                    .append(imgUrl)
                    .append("' alt='")
                    .append(StringUtils.hasText(image.getCaption()) ? image.getCaption() : "图片" + (index + 1))
                    .append("' loading='lazy'>");

            if (StringUtils.hasText(image.getCaption())) {
                html.append("<figcaption class='image-caption'>")
                        .append(image.getCaption())
                        .append("</figcaption>");
            }
        }

        html.append("</figure>");
    }
}
